/*
 * Created on Mar 20, 2013
 * Created by Paul Gardner
 * 
 * Copyright 2013 Azureus Software, Inc.  All rights reserved.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; version 2 of the License only.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
 */

package com.aelitis.azureus.core.tag.impl;

import java.io.File;
import java.util.Set;

import org.gudy.azureus2.core3.internat.MessageText;
import org.gudy.azureus2.core3.util.Debug;
import org.gudy.azureus2.core3.util.ListenerManager;
import org.gudy.azureus2.core3.util.ListenerManagerDispatcher;
import org.gudy.azureus2.core3.util.SimpleTimer;

import com.aelitis.azureus.core.tag.Tag;
import com.aelitis.azureus.core.tag.TagException;
import com.aelitis.azureus.core.tag.TagFeatureFileLocation;
import com.aelitis.azureus.core.tag.TagFeatureProperties;
import com.aelitis.azureus.core.tag.TagFeatureProperties.TagProperty;
import com.aelitis.azureus.core.tag.TagFeatureProperties.TagPropertyListener;
import com.aelitis.azureus.core.tag.TagFeatureRSSFeed;
import com.aelitis.azureus.core.tag.TagFeatureRateLimit;
import com.aelitis.azureus.core.tag.TagListener;
import com.aelitis.azureus.core.tag.Taggable;
import com.aelitis.azureus.core.util.CopyOnWriteList;

public abstract class TagBase implements Tag, SimpleTimer.TimerTickReceiver {
    protected static final String AT_RATELIMIT_UP = "rl.up";
    protected static final String AT_RATELIMIT_DOWN = "rl.down";
    protected static final String AT_VISIBLE = "vis";
    protected static final String AT_PUBLIC = "pub";
    protected static final String AT_GROUP = "gr";
    protected static final String AT_CAN_BE_PUBLIC = "canpub";
    protected static final String AT_ORIGINAL_NAME = "oname";
    protected static final String AT_IMAGE_ID = "img.id";
    protected static final String AT_COLOR_ID = "col.rgb";
    protected static final String AT_RSS_ENABLE = "rss.enable";
    protected static final String AT_RATELIMIT_UP_PRI = "rl.uppri";
    protected static final String AT_XCODE_TARGET = "xcode.to";
    protected static final String AT_FL_MOVE_COMP = "fl.comp";
    protected static final String AT_FL_COPY_COMP = "fl.copy";
    protected static final String AT_FL_INIT_LOC = "fl.init";
    protected static final String AT_RATELIMIT_MIN_SR = "rl.minsr";
    protected static final String AT_RATELIMIT_MAX_SR = "rl.maxsr";
    protected static final String AT_PROPERTY_PREFX = "pp.";

    private static final String[] EMPTY_STRING_LIST = {};

    private TagTypeBase tag_type;

    private int tag_id;
    private String tag_name;

    private static final int TL_ADD = 1;
    private static final int TL_REMOVE = 2;
    private static final int TL_SYNC = 3;

    private ListenerManager<TagListener> t_listeners = ListenerManager.createManager("TagListeners", new ListenerManagerDispatcher<TagListener>() {
        public void dispatch(TagListener listener, int type, Object value) {
            if (type == TL_ADD) {

                listener.taggableAdded(TagBase.this, (Taggable) value);

            } else if (type == TL_REMOVE) {

                listener.taggableRemoved(TagBase.this, (Taggable) value);

            } else if (type == TL_SYNC) {

                listener.taggableSync(TagBase.this);
            }
        }
    });

    private Boolean is_visible;
    private Boolean is_public;
    private String group;

    private TagFeatureRateLimit tag_rl;
    private TagFeatureRSSFeed tag_rss;
    private TagFeatureFileLocation tag_fl;

    protected TagBase(TagTypeBase _tag_type, int _tag_id, String _tag_name) {
        tag_type = _tag_type;
        tag_id = _tag_id;
        tag_name = _tag_name;

        if (getManager().isEnabled()) {

            is_visible = readBooleanAttribute(AT_VISIBLE, null);
            is_public = readBooleanAttribute(AT_PUBLIC, null);
            group = readStringAttribute(AT_GROUP, null);

            if (this instanceof TagFeatureRateLimit) {

                tag_rl = (TagFeatureRateLimit) this;
            }

            if (this instanceof TagFeatureRSSFeed) {

                tag_rss = (TagFeatureRSSFeed) this;

                if (tag_rss.isTagRSSFeedEnabled()) {

                    getManager().checkRSSFeeds(this, true);
                }
            }

            if (this instanceof TagFeatureFileLocation) {

                tag_fl = (TagFeatureFileLocation) this;
            }
        }
    }

    protected void addTag() {
        if (getManager().isEnabled()) {

            tag_type.addTag(this);
        }
    }

    protected TagManagerImpl getManager() {
        return (tag_type.getTagManager());
    }

    public TagTypeBase getTagType() {
        return (tag_type);
    }

    public int getTagID() {
        return (tag_id);
    }

    public long getTagUID() {
        return ((((long) getTagType().getTagType()) << 32) | tag_id);
    }

    protected String getTagNameRaw() {
        return (tag_name);
    }

    public String getTagName(boolean localize) {
        if (localize) {

            if (tag_name.startsWith("tag.")) {

                return (MessageText.getString(tag_name));

            } else {

                return (tag_name);
            }
        } else {

            if (tag_name.startsWith("tag.")) {

                return (tag_name);

            } else {

                String original_name = readStringAttribute(AT_ORIGINAL_NAME, null);

                if (original_name != null && original_name.startsWith("tag.")) {

                    return (original_name);
                }

                return ("!" + tag_name + "!");
            }
        }
    }

    public void setTagName(String name)

    throws TagException {
        if (getTagType().isTagTypeAuto()) {

            throw (new TagException("Not supported"));
        }

        if (tag_name.startsWith("tag.")) {

            String original_name = readStringAttribute(AT_ORIGINAL_NAME, null);

            if (original_name == null) {

                writeStringAttribute(AT_ORIGINAL_NAME, tag_name);
            }
        }

        tag_name = name;

        tag_type.fireChanged(this);
    }

    // public

    public boolean isPublic() {
        boolean pub = is_public == null ? getPublicDefault() : is_public;

        if (pub) {

            if (isTagAuto()) {

                pub = false;
            }
        }

        return (pub);
    }

    public void setPublic(boolean v) {
        if (is_public == null || v != is_public) {

            if (v && !canBePublic()) {

                Debug.out("Invalid attempt to set public");

                return;
            }

            is_public = v;

            writeBooleanAttribute(AT_PUBLIC, v);

            tag_type.fireChanged(this);
        }
    }

    protected boolean getPublicDefault() {
        if (!getCanBePublicDefault()) {

            return (false);
        }

        return (tag_type.getTagManager().getTagPublicDefault());
    }

    public void setCanBePublic(boolean can_be_public) {
        writeBooleanAttribute(AT_CAN_BE_PUBLIC, can_be_public);

        if (!can_be_public) {

            if (isPublic()) {

                setPublic(false);
            }
        }
    }

    public boolean canBePublic() {
        return (readBooleanAttribute(AT_CAN_BE_PUBLIC, getCanBePublicDefault()));
    }

    protected boolean getCanBePublicDefault() {
        return (true);
    }

    public boolean isTagAuto() {
        return (false);
    }

    // visible

    public boolean isVisible() {
        return (is_visible == null ? getVisibleDefault() : is_visible);
    }

    public void setVisible(boolean v) {
        if (is_visible == null || v != is_visible) {

            is_visible = v;

            writeBooleanAttribute(AT_VISIBLE, v);

            tag_type.fireChanged(this);
        }
    }

    public String getGroup() {
        return (group);
    }

    public void setGroup(String new_group) {
        if (group == null && new_group == null) {

            return;
        }

        if (group == null || new_group == null || !group.equals(new_group)) {

            group = new_group;

            writeStringAttribute(AT_GROUP, new_group);

            tag_type.fireChanged(this);
        }
    }

    protected boolean getVisibleDefault() {
        return (true);
    }

    public String getImageID() {
        return (readStringAttribute(AT_IMAGE_ID, null));
    }

    public void setImageID(String id) {
        writeStringAttribute(AT_IMAGE_ID, id);
    }

    private int[] decodeRGB(String str) {
        if (str == null) {

            return (null);
        }

        String[] bits = str.split(",");

        if (bits.length != 3) {

            return (null);
        }

        int[] rgb = new int[3];

        for (int i = 0; i < bits.length; i++) {

            try {

                rgb[i] = Integer.parseInt(bits[i]);

            } catch (Throwable e) {

                return (null);
            }
        }

        return (rgb);
    }

    private String encodeRGB(int[] rgb) {
        if (rgb == null || rgb.length != 3) {

            return (null);
        }

        return (rgb[0] + "," + rgb[1] + "," + rgb[2]);
    }

    public int[] getColor() {
        int[] result = decodeRGB(readStringAttribute(AT_COLOR_ID, null));

        if (result == null) {

            result = tag_type.getColorDefault();
        }

        return (result);
    }

    public void setColor(int[] rgb) {
        writeStringAttribute(AT_COLOR_ID, encodeRGB(rgb));

        tag_type.fireChanged(this);
    }

    public boolean isTagRSSFeedEnabled() {
        if (tag_rss != null) {

            return (readBooleanAttribute(AT_RSS_ENABLE, false));
        }

        return (false);
    }

    public void setTagRSSFeedEnabled(boolean enable) {
        if (tag_rss != null) {

            if (isTagRSSFeedEnabled() != enable) {

                writeBooleanAttribute(AT_RSS_ENABLE, enable);

                tag_type.fireChanged(this);

                tag_type.getTagManager().checkRSSFeeds(this, enable);
            }
        }
    }

    // initial save location

    public boolean supportsTagInitialSaveFolder() {
        return (false);
    }

    public File getTagInitialSaveFolder() {
        if (tag_fl != null) {

            String str = readStringAttribute(AT_FL_INIT_LOC, null);

            if (str == null) {

                return (null);

            } else {

                return (new File(str));
            }
        }

        return (null);
    }

    public void setTagInitialSaveFolder(File folder) {
        if (tag_fl != null) {

            File existing = getTagInitialSaveFolder();

            if (existing == null && folder == null) {

                return;

            } else if (existing == null || folder == null || !existing.equals(folder)) {

                writeStringAttribute(AT_FL_INIT_LOC, folder == null ? null : folder.getAbsolutePath());

                tag_type.fireChanged(this);
            }
        }
    }

    // move on complete

    public boolean supportsTagMoveOnComplete() {
        return (false);
    }

    public File getTagMoveOnCompleteFolder() {
        if (tag_fl != null) {

            String str = readStringAttribute(AT_FL_MOVE_COMP, null);

            if (str == null) {

                return (null);

            } else {

                return (new File(str));
            }
        }

        return (null);
    }

    public void setTagMoveOnCompleteFolder(File folder) {
        if (tag_fl != null) {

            File existing = getTagMoveOnCompleteFolder();

            if (existing == null && folder == null) {

                return;

            } else if (existing == null || folder == null || !existing.equals(folder)) {

                writeStringAttribute(AT_FL_MOVE_COMP, folder == null ? null : folder.getAbsolutePath());

                tag_type.fireChanged(this);
            }
        }
    }

    // copy on complete

    public boolean supportsTagCopyOnComplete() {
        return (false);
    }

    public File getTagCopyOnCompleteFolder() {
        if (tag_fl != null) {

            String str = readStringAttribute(AT_FL_COPY_COMP, null);

            if (str == null) {

                return (null);

            } else {

                return (new File(str));
            }
        }

        return (null);
    }

    public void setTagCopyOnCompleteFolder(File folder) {
        if (tag_fl != null) {

            File existing = getTagCopyOnCompleteFolder();

            if (existing == null && folder == null) {

                return;

            } else if (existing == null || folder == null || !existing.equals(folder)) {

                writeStringAttribute(AT_FL_COPY_COMP, folder == null ? null : folder.getAbsolutePath());

                tag_type.fireChanged(this);
            }
        }
    }

    // min ratio

    public int getTagMinShareRatio() {
        return (-1);
    }

    public void setTagMinShareRatio(int sr) {
        Debug.out("not supported");
    }

    // max ratio

    public int getTagMaxShareRatio() {
        return (-1);
    }

    public void setTagMaxShareRatio(int sr) {
        Debug.out("not supported");
    }

    public TagProperty[] getSupportedProperties() {
        return (new TagProperty[0]);
    }

    public TagProperty getProperty(String name) {
        TagProperty[] props = getSupportedProperties();

        for (TagProperty prop : props) {

            if (prop.getName(false) == name) {

                return (prop);
            }
        }

        return (null);
    }

    protected TagProperty createTagProperty(String name, int type) {
        return (new TagPropertyImpl(name, type));
    }

    public void addTaggable(Taggable t) {
        t_listeners.dispatch(TL_ADD, t);

        tag_type.taggableAdded(this, t);

        tag_type.fireChanged(this);
    }

    public void removeTaggable(Taggable t) {
        t_listeners.dispatch(TL_REMOVE, t);

        tag_type.taggableRemoved(this, t);

        tag_type.fireChanged(this);
    }

    protected void sync() {
        t_listeners.dispatch(TL_SYNC, null);

        tag_type.taggableSync(this);
    }

    public void removeTag() {
        boolean was_rss = isTagRSSFeedEnabled();

        tag_type.removeTag(this);

        if (was_rss) {

            tag_type.getTagManager().checkRSSFeeds(this, false);
        }
    }

    public String getDescription() {
        return (null);
    }

    public void addTagListener(TagListener listener, boolean fire_for_existing) {
        t_listeners.addListener(listener);

        if (fire_for_existing) {

            for (Taggable t : getTagged()) {

                listener.taggableAdded(this, t);
            }
        }
    }

    protected void destroy() {
        Set<Taggable> taggables = getTagged();

        for (Taggable t : taggables) {

            t_listeners.dispatch(TL_REMOVE, t);

            tag_type.taggableRemoved(this, t);
        }
    }

    public void removeTagListener(TagListener listener) {
        t_listeners.removeListener(listener);
    }

    protected Boolean readBooleanAttribute(String attr, Boolean def) {
        return (tag_type.readBooleanAttribute(this, attr, def));
    }

    protected boolean writeBooleanAttribute(String attr, boolean value) {
        return (tag_type.writeBooleanAttribute(this, attr, value));
    }

    protected long readLongAttribute(String attr, long def) {
        return (tag_type.readLongAttribute(this, attr, def));
    }

    protected void writeLongAttribute(String attr, long value) {
        tag_type.writeLongAttribute(this, attr, value);
    }

    protected String readStringAttribute(String attr, String def) {
        return (tag_type.readStringAttribute(this, attr, def));
    }

    protected void writeStringAttribute(String attr, String value) {
        tag_type.writeStringAttribute(this, attr, value);
    }

    protected String[] readStringListAttribute(String attr, String[] def) {
        return (tag_type.readStringListAttribute(this, attr, def));
    }

    protected boolean writeStringListAttribute(String attr, String[] value) {
        return (tag_type.writeStringListAttribute(this, attr, value));
    }

    private static final int HISTORY_MAX_SECS = 30 * 60;
    private volatile boolean history_retention_required;
    private long[] history;
    private int history_pos;
    private boolean history_wrapped;
    private boolean timer_registered;

    public void setRecentHistoryRetention(boolean required) {
        if (tag_rl == null || !tag_rl.supportsTagRates()) {

            return;
        }

        synchronized (this) {

            if (required) {

                if (!history_retention_required) {

                    history = new long[HISTORY_MAX_SECS];

                    history_pos = 0;

                    history_retention_required = true;

                    if (!timer_registered) {

                        SimpleTimer.addTickReceiver(this);

                        timer_registered = true;
                    }
                }
            } else {

                history = null;

                history_retention_required = false;

                if (timer_registered) {

                    SimpleTimer.removeTickReceiver(this);

                    timer_registered = false;
                }
            }
        }
    }

    public int[][] getRecentHistory() {
        synchronized (this) {

            if (history == null) {

                return (new int[2][0]);

            } else {

                int entries = history_wrapped ? HISTORY_MAX_SECS : history_pos;
                int start = history_wrapped ? history_pos : 0;

                int[][] result = new int[2][entries];

                int pos = start;

                for (int i = 0; i < entries; i++) {

                    if (pos == HISTORY_MAX_SECS) {

                        pos = 0;
                    }

                    long entry = history[pos++];

                    int send_rate = (int) ((entry >> 32) & 0xffffffffL);
                    int recv_rate = (int) ((entry) & 0xffffffffL);

                    result[0][i] = send_rate;
                    result[1][i] = recv_rate;
                }

                return (result);
            }
        }
    }

    public void tick(long mono_now, int count) {
        if (!history_retention_required) {

            return;
        }

        long send_rate = tag_rl.getTagCurrentUploadRate();
        long receive_rate = tag_rl.getTagCurrentDownloadRate();

        long entry = (((send_rate) << 32) & 0xffffffff00000000L) | (((receive_rate) & 0x00000000ffffffffL));

        synchronized (this) {

            if (history != null) {

                history[history_pos++] = entry;

                if (history_pos == HISTORY_MAX_SECS) {

                    history_pos = 0;
                    history_wrapped = true;
                }
            }
        }
    }

    private class TagPropertyImpl implements TagProperty {
        private String name;
        private int type;

        private CopyOnWriteList<TagPropertyListener> listeners = new CopyOnWriteList<TagPropertyListener>();

        private TagPropertyImpl(String _name, int _type) {
            name = _name;
            type = _type;
        }

        public Tag getTag() {
            return (TagBase.this);
        }

        public int getType() {
            return (type);
        }

        public String getName(boolean localize) {
            if (localize) {

                return (MessageText.getString("tag.property." + name));

            } else {

                return (name);
            }
        }

        public void setStringList(String[] value) {
            if (writeStringListAttribute(AT_PROPERTY_PREFX + name, value)) {

                for (TagPropertyListener l : listeners) {

                    try {
                        l.propertyChanged(this);

                    } catch (Throwable e) {

                        Debug.out(e);
                    }
                }

                tag_type.fireChanged(TagBase.this);
            }
        }

        public String[] getStringList() {
            return (readStringListAttribute(AT_PROPERTY_PREFX + name, EMPTY_STRING_LIST));
        }

        public void setBoolean(boolean value) {
            if (writeBooleanAttribute(AT_PROPERTY_PREFX + name, value)) {

                for (TagPropertyListener l : listeners) {

                    try {
                        l.propertyChanged(this);

                    } catch (Throwable e) {

                        Debug.out(e);
                    }
                }

                tag_type.fireChanged(TagBase.this);
            }
        }

        public Boolean getBoolean() {
            return (readBooleanAttribute(AT_PROPERTY_PREFX + name, null));
        }

        public String getString() {
            String value = null;

            switch (getType()) {
                case TagFeatureProperties.PT_STRING_LIST: {
                    String[] vals = getStringList();

                    if (vals != null && vals.length > 0) {
                        value = "";

                        if (getName(false).equals(TagFeatureProperties.PR_TRACKER_TEMPLATES)) {

                            String str_merge = MessageText.getString("label.merge");
                            String str_replace = MessageText.getString("label.replace");
                            String str_remove = MessageText.getString("Button.remove");

                            for (String val : vals) {
                                String[] bits = val.split(":");
                                String type = bits[0];
                                String str = bits[1];

                                if (type.equals("m")) {
                                    str += ": " + str_merge;
                                } else if (type.equals("r")) {
                                    str += ": " + str_replace;
                                } else {
                                    str += ": " + str_remove;
                                }
                                value += (value.length() == 0 ? "" : ",") + str;
                            }
                        } else {
                            for (String val : vals) {
                                value += (value.length() == 0 ? "" : ",") + val;
                            }
                        }
                    }
                    break;
                }
                case TagFeatureProperties.PT_BOOLEAN: {
                    Boolean val = getBoolean();
                    if (val != null) {
                        value = String.valueOf(val);
                    }
                    break;
                }
                default: {
                    value = "Unknown type";
                }
            }

            if (value == null) {

                return ("");

            } else {

                return (getName(true) + "=" + value);
            }
        }

        public void addListener(TagPropertyListener listener) {
            listeners.add(listener);
        }

        public void removeListener(TagPropertyListener listener) {
            listeners.remove(listener);
        }

        public void syncListeners() {
            for (TagPropertyListener l : listeners) {

                try {
                    l.propertySync(this);

                } catch (Throwable e) {

                    Debug.out(e);
                }
            }
        }
    }
}
